<template>
  <div class="webgl-starfield">
    <h1>WEBGL闪耀星际</h1>
    <p>使用WebGL实现闪耀星际效果，展示如何使用片元着色器创建动态的星际网络效果。</p>

    <el-card class="demo-section">
      <template #header>
        <div class="card-header">
          <h3>闪耀星际效果展示</h3>
        </div>
      </template>
      <div class="demo-content">
        <canvas ref="demoCanvas" width="600" height="400"></canvas>
        <div class="control-panel">
          <el-form :model="starfieldForm" label-width="100px">
            <el-form-item label="动画速度">
              <el-slider
                v-model="starfieldForm.speed"
                :min="0.1"
                :max="3.0"
                :step="0.1"
                @change="updateStarfield"
              />
            </el-form-item>
          </el-form>
        </div>
      </div>
    </el-card>

    <el-card class="code-section">
      <template #header>
        <div class="card-header">
          <h3>着色器代码</h3>
        </div>
      </template>
      <div class="code-content">
        <pre><code class="glsl">
  // 顶点着色器
  attribute vec4 a_Position;
  void main() {
    gl_Position = a_Position;
  }
        </code></pre>
        <pre><code class="glsl">
  // 片元着色器
  precision mediump float;
  uniform vec2 iResolution;  // 画布分辨率
  uniform float iTime;       // 当前时间
  uniform float iSpeed;      // 动画速度

  #define S(a,b,t) smoothstep(a,b,t)
  #define saturate(t) clamp(t, 0.0, 1.0)

  // 生成随机数
  float N21(vec2 p) {
    p = fract(p*vec2(233.34,851.73));
    p += dot(p, p+23.45);
    return fract(p.x*p.y);
  }

  vec2 N22(vec2 p) {
    float n = N21(p);
    return vec2(n, N21(p+n));
  }

  // 计算点到线段的距离
  float DistLine(vec2 p, vec2 a, vec2 b) {
    vec2 pa = p - a;
    vec2 ba = b - a;
    float t = saturate(dot(pa, ba) / dot(ba, ba));
    return length(pa - ba * t);
  }

  // 绘制线段
  float Line(vec2 p, vec2 a, vec2 b) {
    float d = DistLine(p, a, b);
    float m = S(0.02, 0.01, d);
    float d2 = distance(a, b);
    m *= S(1.2, 0.8, d2)*0.5 + S(0.05, 0.03, abs(d2-0.75));
    return m;
  }

  // 获取点的位置
  vec2 GetPos(vec2 id, vec2 offset) {
    return sin(N22(id+offset)*iTime*iSpeed)*0.4+offset;
  }

  // 绘制一层星际网络
  float Layer(vec2 uv) {
    vec2 gv = fract(uv) - 0.5;
    vec2 id = floor(uv);
    float m = 0.0;

    // 放置点
    vec2 p[9];

    // 使用常量索引替代循环变量
    p[0] = GetPos(id, vec2(-1.0, -1.0));
    p[1] = GetPos(id, vec2(-1.0, 0.0));
    p[2] = GetPos(id, vec2(-1.0, 1.0));
    p[3] = GetPos(id, vec2(0.0, -1.0));
    p[4] = GetPos(id, vec2(0.0, 0.0));
    p[5] = GetPos(id, vec2(0.0, 1.0));
    p[6] = GetPos(id, vec2(1.0, -1.0));
    p[7] = GetPos(id, vec2(1.0, 0.0));
    p[8] = GetPos(id, vec2(1.0, 1.0));

    float t = iTime * iSpeed * 10.0;

    // 使用常量索引替代循环
    m += Line(gv, p[0], p[4]);
    vec2 j0 = (p[0]-gv)*20.0;
    float sparkle0 = 1.0/dot(j0,j0);
    m += sparkle0 * (sin(t+fract(p[0].x)*10.0)*0.5+0.5);

    m += Line(gv, p[1], p[4]);
    vec2 j1 = (p[1]-gv)*20.0;
    float sparkle1 = 1.0/dot(j1,j1);
    m += sparkle1 * (sin(t+fract(p[1].x)*10.0)*0.5+0.5);

    m += Line(gv, p[2], p[4]);
    vec2 j2 = (p[2]-gv)*20.0;
    float sparkle2 = 1.0/dot(j2,j2);
    m += sparkle2 * (sin(t+fract(p[2].x)*10.0)*0.5+0.5);

    m += Line(gv, p[3], p[4]);
    vec2 j3 = (p[3]-gv)*20.0;
    float sparkle3 = 1.0/dot(j3,j3);
    m += sparkle3 * (sin(t+fract(p[3].x)*10.0)*0.5+0.5);

    m += Line(gv, p[5], p[4]);
    vec2 j5 = (p[5]-gv)*20.0;
    float sparkle5 = 1.0/dot(j5,j5);
    m += sparkle5 * (sin(t+fract(p[5].x)*10.0)*0.5+0.5);

    m += Line(gv, p[6], p[4]);
    vec2 j6 = (p[6]-gv)*20.0;
    float sparkle6 = 1.0/dot(j6,j6);
    m += sparkle6 * (sin(t+fract(p[6].x)*10.0)*0.5+0.5);

    m += Line(gv, p[7], p[4]);
    vec2 j7 = (p[7]-gv)*20.0;
    float sparkle7 = 1.0/dot(j7,j7);
    m += sparkle7 * (sin(t+fract(p[7].x)*10.0)*0.5+0.5);

    m += Line(gv, p[8], p[4]);
    vec2 j8 = (p[8]-gv)*20.0;
    float sparkle8 = 1.0/dot(j8,j8);
    m += sparkle8 * (sin(t+fract(p[8].x)*10.0)*0.5+0.5);

    m += Line(gv, p[1], p[3]);
    m += Line(gv, p[1], p[5]);
    m += Line(gv, p[7], p[3]);
    m += Line(gv, p[7], p[5]);

    return m;
  }

  void main() {
    vec2 uv = (gl_FragCoord.xy - 0.5 * iResolution.xy) / iResolution.y;

    float t = iTime * iSpeed * 0.1;
    float m = 0.0;

    // 添加旋转效果
    float s = sin(t);
    float c = cos(t);
    mat2 rot = mat2(c, -s, s, c);
    uv *= rot;

    for(float i = 0.0; i < 1.0; i += 1.0/4.0) {
      float z = fract(i + t);
      float size = mix(10.0, 0.5, z);
      float fade = S(0.0, 0.5, z) * S(1.0, 0.9, z);
      m += Layer(uv * size + i * 20.0) * fade;
    }

    vec3 base = sin(t*5.0*vec3(0.345, 0.456, 0.657))*0.4+0.6;
    vec3 col = vec3(m) * base;

    // 添加渐变效果
    float gradient = uv.y;
    col -= base * gradient * 0.4;

    gl_FragColor = vec4(col, 1.0);
  }
        </code></pre>
      </div>
    </el-card>
  </div>
</template>

<script setup lang="ts">
import { ref, onMounted, reactive } from 'vue'
import { WebGLUtils } from '@/utils/webgl'

const demoCanvas = ref<HTMLCanvasElement | null>(null)
let gl: WebGLRenderingContext | null = null
let program: WebGLProgram | null = null
// 创建WebGL工具实例
const webglUtils = ref<WebGLUtils | null>(null)
// 表单数据
const starfieldForm = reactive({
  speed: 1.0
})

// 初始化顶点缓冲区
function initVertexBuffers(gl: WebGLRenderingContext, program: WebGLProgram) {
  // 创建覆盖整个canvas的矩形
  const vertices = new Float32Array([
    -1.0, 1.0,
    -1.0, -1.0,
    1.0, 1.0,
    1.0, -1.0
  ])

  // 创建缓冲区
  const vertexBuffer = gl.createBuffer()
  gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer)
  gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW)

  // 获取attribute变量的位置
  const a_Position = gl.getAttribLocation(program, 'a_Position')

  // 将缓冲区分配给a_Position变量
  gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, 0, 0)
  gl.enableVertexAttribArray(a_Position)
}

// 渲染场景
function render() {
  if (!gl || !program || !demoCanvas.value) return

  // 设置视口
  gl.viewport(0, 0, demoCanvas.value.width, demoCanvas.value.height)

  // 设置清除颜色和清除缓冲区
  gl.clearColor(0.0, 0.0, 0.0, 1.0)
  gl.clear(gl.COLOR_BUFFER_BIT)

  // 获取uniform变量位置
  const iResolution = gl.getUniformLocation(program, 'iResolution')
  const iTime = gl.getUniformLocation(program, 'iTime')
  const iSpeed = gl.getUniformLocation(program, 'iSpeed')

  // 设置uniform变量值
  gl.uniform2f(iResolution, demoCanvas.value.width, demoCanvas.value.height)
  gl.uniform1f(iTime, performance.now() / 1000.0)
  gl.uniform1f(iSpeed, starfieldForm.speed)

  // 绘制矩形
  gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4)

  // 请求下一帧
  requestAnimationFrame(render)
}

// 更新星际效果
function updateStarfield() {
  render()
}

// 初始化WebGL
const initWebGL = () => {
  if (!demoCanvas.value) return

  // 获取WebGL上下文
  gl = demoCanvas.value.getContext('webgl')
  if (!gl) {
    console.error('Failed to get WebGL context')
    return
  }

  // 创建WebGL工具实例
  webglUtils.value = new WebGLUtils(gl)

  // 顶点着色器源码
  const vertexShaderSource = `
    attribute vec4 a_Position;
    void main() {
      gl_Position = a_Position;
    }
  `

  // 片元着色器源码
  const fragmentShaderSource = `
    precision mediump float;
    uniform vec2 iResolution;  // 画布分辨率
    uniform float iTime;       // 当前时间
    uniform float iSpeed;      // 动画速度

    #define S(a,b,t) smoothstep(a,b,t)
    #define saturate(t) clamp(t, 0.0, 1.0)

    // 生成随机数
    float N21(vec2 p) {
      p = fract(p*vec2(233.34,851.73));
      p += dot(p, p+23.45);
      return fract(p.x*p.y);
    }

    vec2 N22(vec2 p) {
      float n = N21(p);
      return vec2(n, N21(p+n));
    }

    // 计算点到线段的距离
    float DistLine(vec2 p, vec2 a, vec2 b) {
      vec2 pa = p - a;
      vec2 ba = b - a;
      float t = saturate(dot(pa, ba) / dot(ba, ba));
      return length(pa - ba * t);
    }

    // 绘制线段
    float Line(vec2 p, vec2 a, vec2 b) {
      float d = DistLine(p, a, b);
      float m = S(0.02, 0.01, d);
      float d2 = distance(a, b);
      m *= S(1.2, 0.8, d2)*0.5 + S(0.05, 0.03, abs(d2-0.75));
      return m;
    }

    // 获取点的位置
    vec2 GetPos(vec2 id, vec2 offset) {
      return sin(N22(id+offset)*iTime*iSpeed)*0.4+offset;
    }

    // 绘制一层星际网络
    float Layer(vec2 uv) {
      vec2 gv = fract(uv) - 0.5;
      vec2 id = floor(uv);
      float m = 0.0;

      // 放置点
      vec2 p[9];

      // 使用常量索引替代循环变量
      p[0] = GetPos(id, vec2(-1.0, -1.0));
      p[1] = GetPos(id, vec2(-1.0, 0.0));
      p[2] = GetPos(id, vec2(-1.0, 1.0));
      p[3] = GetPos(id, vec2(0.0, -1.0));
      p[4] = GetPos(id, vec2(0.0, 0.0));
      p[5] = GetPos(id, vec2(0.0, 1.0));
      p[6] = GetPos(id, vec2(1.0, -1.0));
      p[7] = GetPos(id, vec2(1.0, 0.0));
      p[8] = GetPos(id, vec2(1.0, 1.0));

      float t = iTime * iSpeed * 10.0;

      // 使用常量索引替代循环
      m += Line(gv, p[0], p[4]);
      vec2 j0 = (p[0]-gv)*200.0;
      float sparkle0 = 1.0/dot(j0,j0);
      m += sparkle0 * (sin(t+fract(p[0].x)*10.0)*0.5+0.5);

      m += Line(gv, p[1], p[4]);
      vec2 j1 = (p[1]-gv)*200.0;
      float sparkle1 = 1.0/dot(j1,j1);
      m += sparkle1 * (sin(t+fract(p[1].x)*10.0)*0.5+0.5);

      m += Line(gv, p[2], p[4]);
      vec2 j2 = (p[2]-gv)*200.0;
      float sparkle2 = 1.0/dot(j2,j2);
      m += sparkle2 * (sin(t+fract(p[2].x)*10.0)*0.5+0.5);

      m += Line(gv, p[3], p[4]);
      vec2 j3 = (p[3]-gv)*200.0;
      float sparkle3 = 1.0/dot(j3,j3);
      m += sparkle3 * (sin(t+fract(p[3].x)*10.0)*0.5+0.5);

      m += Line(gv, p[5], p[4]);
      vec2 j5 = (p[5]-gv)*200.0;
      float sparkle5 = 1.0/dot(j5,j5);
      m += sparkle5 * (sin(t+fract(p[5].x)*10.0)*0.5+0.5);

      m += Line(gv, p[6], p[4]);
      vec2 j6 = (p[6]-gv)*200.0;
      float sparkle6 = 1.0/dot(j6,j6);
      m += sparkle6 * (sin(t+fract(p[6].x)*10.0)*0.5+0.5);

      m += Line(gv, p[7], p[4]);
      vec2 j7 = (p[7]-gv)*200.0;
      float sparkle7 = 1.0/dot(j7,j7);
      m += sparkle7 * (sin(t+fract(p[7].x)*10.0)*0.5+0.5);

      m += Line(gv, p[8], p[4]);
      vec2 j8 = (p[8]-gv)*200.0;
      float sparkle8 = 1.0/dot(j8,j8);
      m += sparkle8 * (sin(t+fract(p[8].x)*10.0)*0.5+0.5);

      m += Line(gv, p[1], p[3]);
      m += Line(gv, p[1], p[5]);
      m += Line(gv, p[7], p[3]);
      m += Line(gv, p[7], p[5]);

      return m;
    }

    void main() {
      vec2 uv = (gl_FragCoord.xy - 0.5 * iResolution.xy) / iResolution.y;

      float t = iTime * iSpeed * 0.1;
      float m = 0.0;

      // 添加旋转效果
      float s = sin(t);
      float c = cos(t);
      mat2 rot = mat2(c, -s, s, c);
      uv *= rot;

      for(float i = 0.0; i < 1.0; i += 1.0/4.0) {
        float z = fract(i + t);
        float size = mix(10.0, 0.5, z);
        float fade = S(0.0, 0.5, z) * S(1.0, 0.9, z);
        m += Layer(uv * size + i * 20.0) * fade;
      }

      vec3 base = sin(t*5.0*vec3(0.345, 0.456, 0.657))*0.4+0.6;
      vec3 col = vec3(m) * base;

      // 添加渐变效果
      float gradient = uv.y;
      col -= base * gradient * 0.4;

      gl_FragColor = vec4(col, 1.0);
    }
  `

  const vertexShader = webglUtils.value.createShader(gl.VERTEX_SHADER, vertexShaderSource)
  const fragmentShader = webglUtils.value.createShader(gl.FRAGMENT_SHADER, fragmentShaderSource)
  if (!vertexShader || !fragmentShader) {
    console.error('Failed to create shaders')
    return
  }

  // 创建着色器程序
  program = webglUtils.value.createProgram(vertexShader, fragmentShader)
  if (!program) {
    console.error('Failed to create program')
    return
  }

  // 使用程序对象
  gl.useProgram(program)

  // 启用混合模式，使星际效果更加明亮
  gl.enable(gl.BLEND)
  gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA)

  // 初始化顶点缓冲区
  initVertexBuffers(gl, program)

  // 开始渲染
  render()
}

// 组件挂载时初始化WebGL
onMounted(() => {
  initWebGL()
})
</script>

<style lang="scss" scoped>
.webgl-starfield {
  max-width: 1200px;
  margin: 0 auto;
  padding: 20px;

  h1 {
    color: #303133;
    margin-bottom: 20px;
  }

  p {
    margin-bottom: 30px;
  }

  .demo-section,
  .code-section {
    margin: 30px 0;

    .card-header {
      display: flex;
      align-items: center;

      h3 {
        margin: 0;
        color: #409EFF;
      }
    }
  }

  .demo-section {
    .demo-content {
      display: flex;
      gap: 20px;
      justify-content: center;

      canvas {
        border: 1px solid #dcdfe6;
        background-color: #000;
      }

      .control-panel {
        flex: 1;
        padding: 20px;
      }
    }
  }

  .code-section {
    .code-content {
      background-color: #f8f9fa;
      padding: 15px;
      border-radius: 4px;
      max-height: 500px;
      overflow-y: auto;

      code {
        font-family: monospace;
        color: #409EFF;
      }

      pre {
        margin: 0;
        white-space: pre-wrap;
      }
    }
  }
}
</style>
